package dcdroplet;

// Import Java JDK Classes
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * Reads in properties from dcdroplet.properties which should be on the classpath.<br/>
 * The general format for DCDroplet properties files is:<br/>
 * <code>
 * @literal ###################################################################################<br/>
 * @literal # dcdroplet.appenders defines any appender names the user wants to use for logging.<br/>
 * @literal # These names can by any alpha-numeric text strings.<br/>
 * @literal # Multiple names can be provided, delimited by commas.<br/>
 * @literal # This property is mandatory.<br/>
 * @literal ###################################################################################<br/>
 * dcdroplet.appenders=APPENDER1,APPENDER2,APPENDER3,...
 * 
 * @literal #########################################################<br/>
 * @literal # dcdroplet.tags defines names for logging message types.<br/>
 * @literal # These types can by any alpha-numeric text strings.<br/>
 * @literal # Multiple types can be provided, delimited by commas.<br/>
 * @literal # This property is optional.<br/>
 * @literal #########################################################<br/>
 * dcdroplet.tags=TAG1,TAG2,TAG3,...
 * 
 * @literal ##############################################################################<br/>
 * @literal # dcdroplet.pattern specifies the pattern for logging messages.<br/>
 * @literal # Cannot have multiple values. Only single values.<br/>
 * @literal # This property is optional.<br/>
 * @literal # dcdroplet.pattern can have any combination of valid keywords and other text.<br/>
 * @literal # If no pattern is given, then only the log message will be logged.<br/>
 * @literal # If the pattern is given, but there is no ${message} keyword present,<br/>
 * @literal #    then the log message will be appended to the end of the pattern.<br/>
 * @literal # Valid keywords for dcdroplet.&lt;NAME&gt;.pattern are:<br/>
 * @literal #    ${class_name}<br/>
 * @literal #    ${tag_name}<br/>
 * @literal #    ${date}<br/>
 * @literal #    ${time}<br/>
 * @literal #    ${message}<br/>
 * @literal ##############################################################################<br/>
 * dcdroplet.pattern=text ${class_name} text ${message} text ...
 * 
 * @literal ######################################################################################<br/>
 * @literal # dcdroplet.&lt;NAME&gt;.tags will override the message types logged by a specific appender.<br/>
 * @literal # &lt;NAME&gt; should be substituted by a name provided by dcdroplet.appenders.<br/>
 * @literal # Multiple types can be provided, delimited by commas.<br/>
 * @literal # This property is optional.<br/>
 * @literal ######################################################################################<br/>
 * dcdroplet.APPENDER1.tags=TAG1,TAG2,TAG3...
 * dcdroplet.APPENDER2.tags=TAG4,TAG5,TAG6...
 * dcdroplet.APPENDER3.tags=TAG1,TAG4,TAG7,...
 * 
 * @literal ###########################################################################<br/>
 * @literal # dcdroplet.&lt;NAME&gt;.out specifies the appender type for a specific appender.<br/>
 * @literal # &lt;NAME&gt; should be substituted by a name provided by dcdroplet.appenders.<br/>
 * @literal # Cannot have multiple values. Only single values.<br/>
 * @literal # This property is mandatory.<br/>
 * @literal # Valid values for dcdroplet.&lt;NAME&gt;.out are:<br/>
 * @literal #    CONSOLE<br/>
 * @literal #    FILE<br/>
 * @literal #    ROLLING_FILE<br/>
 * @literal #    HTML_FILE<br/>
 * @literal #    XML_FILE<br/>
 * @literal ###########################################################################<br/>
 * dcdroplet.APPENDER1.out=CONSOLE
 * dcdroplet.APPENDER2.out=CONSOLE
 * dcdroplet.APPENDER3.out=FILE
 * dcdroplet.APPENDER4.out=HTML_FILE
 * dcdroplet.APPENDER5.out=XML_FILE
 * dcdroplet.APPENDER6.out=ROLLING_FILE
 * 
 * @literal #####################################################################################<br/>
 * @literal # dcdroplet.&lt;NAME&gt;.pattern will override the message pattern for a specific appender.<br/>
 * @literal # &lt;NAME&gt; should be substituted by a name provided by dcdroplet.appenders.<br/>
 * @literal # Cannot have multiple values. Only single values.<br/>
 * @literal # This property is optional.<br/>
 * @literal # dcdroplet.&lt;NAME&gt;.pattern can have any combination of valid keywords and other text.<br/>
 * @literal # Valid keywords for dcdroplet.&lt;NAME&gt;.pattern are:<br/>
 * @literal #    ${class_name}<br/>
 * @literal #    ${tag_name}<br/>
 * @literal #    ${date}<br/>
 * @literal #    ${time}<br/>
 * @literal #    ${message}<br/>
 * @literal #####################################################################################<br/>
 * dcdroplet.APPENDER9.pattern=${class_name} [${tag_name}] ${date} @ ${time}: ${message}
 * 
 * @literal ##############################################################################################<br/>
 * @literal # dcdroplet.&lt;NAME&gt;.file specifies an output file for logging messages for a specific appender.<br/>
 * @literal # &lt;NAME&gt; should be substituted by a name provided by dcdroplet.appenders.<br/>
 * @literal # Cannot have multiple values. Only single values.<br/>
 * @literal # If the user supplied a value for dcdroplet.&lt;NAME&gt;.out, as CONSOLE,<br/>
 * @literal #    then this property should not be provided.  If it is, it is unused.<br/>
 * @literal # If the user supplied a value for dcdroplet.&lt;NAME&gt;.out, as something other than CONSOLE,<br/>
 * @literal #    then this property is mandatory.<br/>
 * @literal ##############################################################################################<br/>
 * dcdroplet.APPENDER3.file=/path/to/test.log
 * dcdroplet.APPENDER4.file=/path/to/test.html
 * dcdroplet.APPENDER5.file=/path/to/test.xml
 * dcdroplet.APPENDER6.file=/path/to/test_roller.log
 * 
 * @literal ##########################################################################################################<br/>
 * @literal # dcdroplet.&lt;NAME&gt;.size specifies the size (in bytes) a rolling file should be before starting a new file.<br/>
 * @literal # &lt;NAME&gt; should be substituted by a name provided by dcdroplet.appenders.<br/>
 * @literal # Cannot have multiple values. Only single values.<br/>
 * @literal # This value should be numeric text, which represents the number of bytes.<br/>
 * @literal # If the user supplied a value for dcdroplet.&lt;NAME&gt;.file, as ROLLING_FILE,<br/>
 * @literal #    then this property is mandatory.<br/>
 * @literal # If the user supplied a value for dcdroplet.&lt;NAME&gt;.file, as something other than ROLLING_FILE,<br/>
 * @literal #    then this property should not be provided.  If it is, it is unused.<br/>
 * @literal ##########################################################################################################<br/>
 * dcdroplet.APPENDER6.size=128
 * </code>
 * @author dca
 */
public class DCDropletProperties extends Properties {
	/** The unique identifier for serialization of this class.<br/> */
	private static final long serialVersionUID = -1362568794577154720L;

	// General Properties

	/** DCDroplet property prefix.<br/> */
	private static final String DCDROPLET = "dcdroplet";

	/** Appenders property.<br/> */
	private static final String DCDROPLET_APPENDERS_PROP = DCDROPLET + ".appenders";

	/** Tags property.<br/> */
	private static final String DCDROPLET_TAGS_PROP = DCDROPLET + ".tags";

	/** Pattern property.<br/> */
	private static final String DCDROPLET_PATTERN_PROP = DCDROPLET + ".pattern";

	// Appender-specific Properties

	/** Appender tags property.<br/> */
	private static final String APPENDER_TAGS_PROP = "tags";

	/** Appender out property.<br/> */
	private static final String APPENDER_OUT_PROP = "out";

	/** Appender pattern property.<br/> */
	private static final String APPENDER_PATTERN_PROP = "pattern";

	/** Appender file property.<br/> */
	private static final String APPENDER_FILE_PROP = "file";

	/** Appender file size property.<br/> */
	private static final String APPENDER_SIZE_PROP = "size";

	// Instance Fields

	/** The map of appenders.<br/> */
	private Map<String, AppenderProperties> appenders = new TreeMap<String, AppenderProperties>();

	/** The general set of tags.<br/> */
	private Set<String> tags = new TreeSet<String>();

	/** The general pattern for log messages.<br/> */
	private String pattern = "";

	/**
	 * Constructor<br/>
	 * Expects dcdroplet.properties to be on the classpath.<br/>
	 * @throws IOException If the properties cannot be loaded.
	 * @see java.util.Properties#load(java.io.InputStream)
	 */
	public DCDropletProperties() throws IOException {
		load(this.getClass().getClassLoader().getResourceAsStream("dcdroplet.properties"));
		init();
	} // END constructor

	/**
	 * Constructor<br/>
	 * Expects the DCDropletPropertyFileName to be on the classpath.<br/>
	 * @param DCDropletPropertyFileName The property file name to read for DCDroplet properties.<br/>
	 * @throws IOException If the properties cannot be loaded.
	 * @see java.util.Properties#load(java.io.InputStream)
	 */
	public DCDropletProperties(final String DCDropletPropertyFileName) throws IOException {
		load(this.getClass().getClassLoader().getResourceAsStream(DCDropletPropertyFileName));
		init();
	} // END constructor

	/**
	 * Constructor<br/>
	 * @param DCDropletPropertyFile The file to read for DCDroplet properties.<br/>
	 * @throws FileNotFoundException If the file is not found.
	 * @throws IOException If the properties cannot be loaded.
	 */
	public DCDropletProperties(final File DCDropletPropertyFile) throws FileNotFoundException, IOException {
		load(new FileInputStream(DCDropletPropertyFile));
		init();
	} // END constructor

	/**
	 * Initializes the instance fields based on properties that have been read from dcdroplet.properties.<br/>
	 */
	private void init() {
		// For each appender defined in the properties file.
		// Appenders are defined in the form of dcdroplet.appenders=APPENDER1,APPENDER2,APPENDER3,...
		for(final String APPENDER_NAME : this.getProperty(DCDROPLET_APPENDERS_PROP).split(",")) {
			// Get the properties read in from the properties file.
			// Appender properties are defined in the form of dcdroplet.APPENDER1.<PROPERTY>=VALUE or dcdroplet.<APPENDER>.<PROPERTY>=<STRING>,<STRING>,<STRING>,...
			final String APPENDER_TAGS    = this.getProperty(DCDROPLET + "." + APPENDER_NAME + "." + APPENDER_TAGS_PROP);
			final String APPENDER_OUT     = this.getProperty(DCDROPLET + "." + APPENDER_NAME + "." + APPENDER_OUT_PROP);
			final String APPENDER_PATTERN = this.getProperty(DCDROPLET + "." + APPENDER_NAME + "." + APPENDER_PATTERN_PROP);
			final String APPENDER_FILE    = this.getProperty(DCDROPLET + "." + APPENDER_NAME + "." + APPENDER_FILE_PROP);
			final String APPENDER_SIZE    = this.getProperty(DCDROPLET + "." + APPENDER_NAME + "." + APPENDER_SIZE_PROP);

			// Create an appender specific to the "out" value.
			if("CONSOLE".equalsIgnoreCase(APPENDER_OUT)) {
				appenders.put(APPENDER_NAME, new ConsoleAppenderProperties());
			} else if("FILE".equalsIgnoreCase(APPENDER_OUT)) {
				appenders.put(APPENDER_NAME, new FileAppenderProperties());
			} else if("ROLLING_FILE".equalsIgnoreCase(APPENDER_OUT)) {
				appenders.put(APPENDER_NAME, new RollingFileAppenderProperties());
			} else if("HTML_FILE".equalsIgnoreCase(APPENDER_OUT)) {
				appenders.put(APPENDER_NAME, new HTMLFileAppenderProperties());
			} else if("XML_FILE".equalsIgnoreCase(APPENDER_OUT)) {
				appenders.put(APPENDER_NAME, new XMLFileAppenderProperties());
			} // END if/else

			// Add appender properties to the appender.
			if(appenders.containsKey(APPENDER_NAME)) {
				appenders.get(APPENDER_NAME).setNameDefinition(APPENDER_NAME);       // Name is defined as a multi-value property.  dcdroplet.appenders=<STRING>,<STRING>,<STRING>,...  For an appender, the name is the same as the key for the appenders map.
				appenders.get(APPENDER_NAME).setTagsDefinition(APPENDER_TAGS);       // Tags are defined as a multi-value property, delineated by commas.  dcdroplet.<APPENDER>.tags=<STRING>,<STRING>,<STRING>,...
				appenders.get(APPENDER_NAME).setOutDefinition(APPENDER_OUT);         // Out is defined as a single-value property.  dcdroplet.<APPENDER>.out=<STRING>
				appenders.get(APPENDER_NAME).setPatternDefinition(APPENDER_PATTERN); // Pattern is defined as a single-value property.  dcdroplet.<APPENDER>.pattern=<STRING>
				appenders.get(APPENDER_NAME).setFileDefinition(APPENDER_FILE);       // File is defined as a single-value property.  dcdroplet.<APPENDER>.file=<PATH>
				appenders.get(APPENDER_NAME).setSizeDefinition(APPENDER_SIZE);       // Size is defined as a single-value property.  dcdroplet.<APPENDER>.size=<###>
			} // END if
		} // END loop

		// Add general tags properties.
		tags.addAll(Arrays.asList(this.getProperty(DCDROPLET_TAGS_PROP).split(",")));

		pattern = this.getProperty(DCDROPLET_PATTERN_PROP);
		if(null == pattern) {
			pattern = "";
		} // END if
	} // END init

	/**
	 * Prints all the properties from the appender map to system out.<br/>
	 */
	public void printProperties() {
		printProperties(System.out);
	} // END printProperties

	/**
	 * Prints all the properties from the appender map to the supplied print stream.<br/>
	 * @param out The print stream to use.<br/>
	 */
	public void printProperties(PrintStream out) {
		if(null != out) {
			out.printf("%s = %s,%n", DCDROPLET_APPENDERS_PROP, appenders.keySet());
			out.printf("%s = %s,%n", DCDROPLET_TAGS_PROP, tags.toString());
			out.printf("%s = %s,%n", DCDROPLET_PATTERN_PROP, pattern.toString());

			out.printf("APPENDER PROPERTIES = {%n");

			int iter = 0;

			for(String key : appenders.keySet()) {
				out.printf("\t%-10s = %s", key, appenders.get(key).toString().replaceAll("^\\{", "{\n\t\t").replaceAll("\\}$", "\n\t}").replaceAll(", ", ",\n\t\t"));
				if(iter++ < appenders.keySet().size() - 1) out.printf(",");
				out.printf("%n");
			} // END loop

			out.printf("}%n");
		} // END if
	} // END printProperties

	/**
	 * Returns the general tag settings.<br/>
	 * This is defined by property name: dcdroplet.tags<br/>
	 * @return tags The general tag settings.<br/>
	 */
	public Set<String> getTags() {
		return tags;
	} // END getTags

	/**
	 * Returns true if the properties file has tags supplied in dcdroplet.properties and those tags contain the supplied tag.<br/>
	 * @param tag The tag name.<br/>
	 * @return true or false.<br/>
	 */
	public boolean hasTag(String tag) {
		return Util.containsTag(getTags(), tag);
	} // END hasTag

	/**
	 * Returns the general pattern setting.<br/>
	 * This is defined by property name: dcdroplet.pattern<br/>
	 * @return pattern The general pattern setting.<br/>
	 */
	public String getPattern() {
		return pattern;
	} // END getPattern

	/**
	 * Returns true if the properties file has a general properties definition.<br/>
	 * @return true or false.<br/>
	 */
	public boolean hasPatternDefinition() {
		return !pattern.isEmpty();
	} // END hasPatternDefinition

	/**
	 * Returns the set of appender names.<br/>
	 * @return appenders.keySet() The appender names.<br/>
	 */
	public Set<String> getAppenders() {
		return appenders.keySet();
	} // END getAppenders

	/**
	 * Returns the properties for a given appender.<br/>
	 * @param appender The appender name.<br/>
	 * @return AppenderProperties The properties for the appender
	 * @throws NoSuchElementException If appender is undefined.<br/>
	 */
	public AppenderProperties getAppenderProperties(String appender) throws NoSuchElementException {
		if(!appenders.containsKey(appender)) {
			throw new NoSuchElementException("Appender, " + appender + ", is not defined.  Check your dcdroplet.properties configuration.");
		} else {
			return appenders.get(appender);
		} // END if/else
	} // END getAppenderProperties
} // END class DCDropletProperties
	